home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2007 December / PCWKCD1207B.iso / Blogowanie poza sfera / Flock 1.0 beta / flock-1.0RC3.en-US.win32.exe / flock / components / FeedProcessor.js < prev    next >
Text File  |  2007-10-18  |  63KB  |  1,899 lines

  1. /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* ***** BEGIN LICENSE BLOCK *****
  3.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4.  *
  5.  * The contents of this file are subject to the Mozilla Public License Version
  6.  * 1.1 (the "License"); you may not use this file except in compliance with
  7.  * the License. You may obtain a copy of the License at
  8.  * http://www.mozilla.org/MPL/
  9.  *
  10.  * Software distributed under the License is distributed on an "AS IS" basis,
  11.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12.  * for the specific language governing rights and limitations under the
  13.  * License.
  14.  *
  15.  * The Original Code is mozilla.org code.
  16.  *
  17.  * The Initial Developer of the Original Code is Robert Sayre.
  18.  * Portions created by the Initial Developer are Copyright (C) 2006
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *   Ben Goodger <beng@google.com>
  23.  *   Myk Melez <myk@mozilla.org>
  24.  *
  25.  * Alternatively, the contents of this file may be used under the terms of
  26.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  27.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  28.  * in which case the provisions of the GPL or the LGPL are applicable instead
  29.  * of those above. If you wish to allow use of your version of this file only
  30.  * under the terms of either the GPL or the LGPL, and not to allow others to
  31.  * use your version of this file under the terms of the MPL, indicate your
  32.  * decision by deleting the provisions above and replace them with the notice
  33.  * and other provisions required by the GPL or the LGPL. If you do not delete
  34.  * the provisions above, a recipient may use your version of this file under
  35.  * the terms of any one of the MPL, the GPL or the LGPL.
  36.  *
  37.  * ***** END LICENSE BLOCK ***** */
  38.  
  39. function LOG(str) {
  40.   dump("*** " + str + "\n");
  41. }
  42.  
  43. const Ci = Components.interfaces;
  44. const Cc = Components.classes;
  45. const Cr = Components.results;
  46.  
  47. const NS_BINDING_ABORTED = 0x804b0002;
  48.  
  49. const IO_CONTRACTID = "@mozilla.org/network/io-service;1"
  50. const BAG_CONTRACTID = "@mozilla.org/hash-property-bag;1"
  51. const ARRAY_CONTRACTID = "@mozilla.org/array;1";
  52. const SAX_CONTRACTID = "@mozilla.org/saxparser/xmlreader;1";
  53. const UNESCAPE_CONTRACTID = "@mozilla.org/feed-unescapehtml;1";
  54.  
  55. var gIoService = Cc[IO_CONTRACTID].getService(Ci.nsIIOService);
  56. var gUnescapeHTML = Cc[UNESCAPE_CONTRACTID].
  57.                     getService(Ci.nsIScriptableUnescapeHTML);
  58.  
  59. const XMLNS = "http://www.w3.org/XML/1998/namespace";
  60. const RSS090NS = "http://my.netscape.com/rdf/simple/0.9/";
  61.  
  62. /***** Some general utils *****/
  63. function strToURI(link, base) {
  64.   var base = base || null;
  65.   try {
  66.     return gIoService.newURI(link, null, base);
  67.   }
  68.   catch(e) {
  69.     return null;
  70.   }
  71. }
  72.  
  73. function isArray(a) {
  74.   return isObject(a) && a.constructor == Array;
  75. }
  76.  
  77. function isObject(a) {
  78.   return (a && typeof a == "object") || isFunction(a);
  79. }
  80.  
  81. function isFunction(a) {
  82.   return typeof a == "function";
  83. }
  84.  
  85. function isIID(a, iid) {
  86.   var rv = false;
  87.   try {
  88.     a.QueryInterface(iid);
  89.     rv = true;
  90.   }
  91.   catch(e) {
  92.   }
  93.   return rv;
  94. }
  95.  
  96. function isIArray(a) {
  97.   return isIID(a, Ci.nsIArray);
  98. }
  99.  
  100. function isIFeedContainer(a) {
  101.   return isIID(a, Ci.nsIFeedContainer);
  102. }
  103.  
  104. function stripTags(someHTML) {
  105.   return someHTML.replace(/<[^>]+>/g,"");
  106. }
  107.  
  108. /**
  109.  * Searches through an array of links and returns a JS array 
  110.  * of matching property bags.
  111.  */
  112. const IANA_URI = "http://www.iana.org/assignments/relation/";
  113. function findAtomLinks(rel, links) {
  114.   var rvLinks = [];
  115.   for (var i = 0; i < links.length; ++i) {
  116.     var linkElement = links.queryElementAt(i, Ci.nsIPropertyBag2);
  117.     // atom:link MUST have @href
  118.     if (bagHasKey(linkElement, "href")) {
  119.       var relAttribute = null;
  120.       if (bagHasKey(linkElement, "rel"))
  121.         relAttribute = linkElement.getPropertyAsAString("rel")
  122.       if ((!relAttribute && rel == "alternate") || relAttribute == rel) {
  123.         rvLinks.push(linkElement);
  124.         continue;
  125.       }
  126.       // catch relations specified by IANA URI 
  127.       if (relAttribute == IANA_URI + rel) {
  128.         rvLinks.push(linkElement);
  129.       }
  130.     }
  131.   }
  132.   return rvLinks;
  133. }
  134.  
  135. function xmlEscape(s) {
  136.   s = s.replace(/&/g, "&");
  137.   s = s.replace(/>/g, ">");
  138.   s = s.replace(/</g, "<");
  139.   s = s.replace(/"/g, """);
  140.   s = s.replace(/'/g, "'");
  141.   return s;
  142. }
  143.  
  144. function arrayContains(array, element) {
  145.   for (var i = 0; i < array.length; ++i) {
  146.     if (array[i] == element) {
  147.       return true;
  148.     }
  149.   }
  150.   return false;
  151. }
  152.  
  153. // XXX add hasKey to nsIPropertyBag
  154. function bagHasKey(bag, key) {
  155.   try {
  156.     bag.getProperty(key);
  157.     return true;
  158.   }
  159.   catch (e) {
  160.     return false;
  161.   }
  162. }
  163.  
  164. function makePropGetter(key) {
  165.   return function FeedPropGetter(bag) {
  166.     try {
  167.       return value = bag.getProperty(key);
  168.     }
  169.     catch(e) {
  170.     }
  171.     return null;
  172.   }
  173. }
  174.  
  175.  
  176.  
  177. /**
  178.  * XXX Thunderbird's W3C-DTF function
  179.  *
  180.  * Converts a W3C-DTF (subset of ISO 8601) date string to an IETF date
  181.  * string.  W3C-DTF is described in this note:
  182.  * http://www.w3.org/TR/NOTE-datetime IETF is obtained via the Date
  183.  * object's toUTCString() method.  The object's toString() method is
  184.  * insufficient because it spells out timezones on Win32
  185.  * (f.e. "Pacific Standard Time" instead of "PST"), which Mail doesn't
  186.  * grok.  For info, see
  187.  * http://lxr.mozilla.org/mozilla/source/js/src/jsdate.c#1526.
  188.  */
  189. const HOURS_TO_MINUTES = 60;
  190. const MINUTES_TO_SECONDS = 60;
  191. const SECONDS_TO_MILLISECONDS = 1000;
  192. const MINUTES_TO_MILLISECONDS = MINUTES_TO_SECONDS * SECONDS_TO_MILLISECONDS;
  193. const HOURS_TO_MILLISECONDS = HOURS_TO_MINUTES * MINUTES_TO_MILLISECONDS;
  194. function W3CToIETFDate(dateString) {
  195.  
  196.   var parts = dateString.match(/(\d{4})(-(\d{2,3}))?(-(\d{2}))?(T(\d{2}):(\d{2})(:(\d{2})(\.(\d+))?)?(Z|([+-])(\d{2}):(\d{2}))?)?/);
  197.  
  198.   // Here's an example of a W3C-DTF date string and what .match returns for it.
  199.   // 
  200.   // date: 2003-05-30T11:18:50.345-08:00
  201.   // date.match returns array values:
  202.   //
  203.   //   0: 2003-05-30T11:18:50-08:00,
  204.   //   1: 2003,
  205.   //   2: -05,
  206.   //   3: 05,
  207.   //   4: -30,
  208.   //   5: 30,
  209.   //   6: T11:18:50-08:00,
  210.   //   7: 11,
  211.   //   8: 18,
  212.   //   9: :50,
  213.   //   10: 50,
  214.   //   11: .345,
  215.   //   12: 345,
  216.   //   13: -08:00,
  217.   //   14: -,
  218.   //   15: 08,
  219.   //   16: 00
  220.  
  221.   // Create a Date object from the date parts.  Note that the Date
  222.   // object apparently can't deal with empty string parameters in lieu
  223.   // of numbers, so optional values (like hours, minutes, seconds, and
  224.   // milliseconds) must be forced to be numbers.
  225.   var date = new Date(parts[1], parts[3] - 1, parts[5], parts[7] || 0,
  226.                       parts[8] || 0, parts[10] || 0, parts[12] || 0);
  227.  
  228.   // We now have a value that the Date object thinks is in the local
  229.   // timezone but which actually represents the date/time in the
  230.   // remote timezone (f.e. the value was "10:00 EST", and we have
  231.   // converted it to "10:00 PST" instead of "07:00 PST").  We need to
  232.   // correct that.  To do so, we're going to add the offset between
  233.   // the remote timezone and UTC (to convert the value to UTC), then
  234.   // add the offset between UTC and the local timezone //(to convert
  235.   // the value to the local timezone).
  236.  
  237.   // Ironically, W3C-DTF gives us the offset between UTC and the
  238.   // remote timezone rather than the other way around, while the
  239.   // getTimezoneOffset() method of a Date object gives us the offset
  240.   // between the local timezone and UTC rather than the other way
  241.   // around.  Both of these are the additive inverse (i.e. -x for x)
  242.   // of what we want, so we have to invert them to use them by
  243.   // multipying by -1 (f.e. if "the offset between UTC and the remote
  244.   // timezone" is -5 hours, then "the offset between the remote
  245.   // timezone and UTC" is -5*-1 = 5 hours).
  246.  
  247.   // Note that if the timezone portion of the date/time string is
  248.   // absent (which violates W3C-DTF, although ISO 8601 allows it), we
  249.   // assume the value to be in UTC.
  250.  
  251.   // The offset between the remote timezone and UTC in milliseconds.
  252.   var remoteToUTCOffset = 0;
  253.   if (parts[13] && parts[13] != "Z") {
  254.     var direction = (parts[14] == "+" ? 1 : -1);
  255.     if (parts[15])
  256.       remoteToUTCOffset += direction * parts[15] * HOURS_TO_MILLISECONDS;
  257.     if (parts[16])
  258.       remoteToUTCOffset += direction * parts[16] * MINUTES_TO_MILLISECONDS;
  259.   }
  260.   remoteToUTCOffset = remoteToUTCOffset * -1; // invert it
  261.  
  262.   // The offset between UTC and the local timezone in milliseconds.
  263.   var UTCToLocalOffset = date.getTimezoneOffset() * MINUTES_TO_MILLISECONDS;
  264.   UTCToLocalOffset = UTCToLocalOffset * -1; // invert it
  265.   date.setTime(date.getTime() + remoteToUTCOffset + UTCToLocalOffset);
  266.  
  267.   return date.toUTCString();
  268. }
  269.  
  270. const RDF_NS = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
  271. // namespace map
  272. var gNamespaces = {
  273.   "http://webns.net/mvcb/":"admin",
  274.   "http://backend.userland.com/rss":"",
  275.   "http://blogs.law.harvard.edu/tech/rss":"",
  276.   "http://www.w3.org/2005/Atom":"atom",
  277.   "http://purl.org/atom/ns#":"atom03",
  278.   "http://purl.org/rss/1.0/modules/content/":"content",
  279.   "http://purl.org/dc/elements/1.1/":"dc",
  280.   "http://purl.org/dc/terms/":"dcterms",
  281.   "http://www.w3.org/1999/02/22-rdf-syntax-ns#":"rdf",
  282.   "http://purl.org/rss/1.0/":"rss1",
  283.   "http://my.netscape.com/rdf/simple/0.9/":"rss1",
  284.   "http://wellformedweb.org/CommentAPI/":"wfw",                              
  285.   "http://purl.org/rss/1.0/modules/wiki/":"wiki", 
  286.   "http://www.w3.org/XML/1998/namespace":"xml"
  287. }
  288.  
  289.  
  290. function FeedResult() {}
  291. FeedResult.prototype = {
  292.   bozo: false,
  293.   doc: null,
  294.   version: null,
  295.   headers: null,
  296.   uri: null,
  297.   stylesheet: null,
  298.  
  299.   registerExtensionPrefix: function FR_registerExtensionPrefix(ns, prefix) {
  300.     throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  301.   },
  302.  
  303.   QueryInterface: function FR_QI(iid) {
  304.     if (iid.equals(Ci.nsIFeedResult) ||
  305.         iid.equals(Ci.nsISupports))
  306.       return this;
  307.  
  308.     throw Cr.NS_ERROR_NOINTERFACE;
  309.   },
  310. }  
  311.  
  312. function Feed() {
  313.   this.subtitle = null;
  314.   this.title = null;
  315.   this.items = Cc[ARRAY_CONTRACTID].createInstance(Ci.nsIMutableArray);
  316.   this.link = null;
  317.   this.id = null;
  318.   this.generator = null;
  319.   this.authors = Cc[ARRAY_CONTRACTID].createInstance(Ci.nsIMutableArray);
  320.   this.contributors = Cc[ARRAY_CONTRACTID].createInstance(Ci.nsIMutableArray);
  321.   this.baseURI = null;
  322.   this.metadataOnly = false;
  323. }
  324.  
  325. Feed.prototype = {
  326.   searchLists: {
  327.     subtitle: ["description","dc:description","rss1:description",
  328.                "atom03:tagline","atom:subtitle"],
  329.     items: ["items","atom03_entries","entries"],
  330.     id: ["atom:id","rdf:about"],
  331.     generator: ["generator"],
  332.     authors : ["authors"],
  333.     contributors: ["contributors"],
  334.     title: ["title","rss1:title", "atom03:title","atom:title"],
  335.     link:  [["link",strToURI],["rss1:link",strToURI]],
  336.     categories: ["categories", "dc:subject"],
  337.     rights: ["atom03:rights","atom:rights"],
  338.     cloud: ["cloud"],
  339.     image: ["image", "rss1:image", "atom:logo"],
  340.     textInput: ["textInput", "rss1:textinput"],
  341.     skipDays: ["skipDays"],
  342.     skipHours: ["skipHours"],
  343.     updated: ["pubDate", "lastBuildDate", "atom03:modified", "dc:date",
  344.               "dcterms:modified", "atom:updated"]
  345.   },
  346.  
  347.   normalize: function Feed_normalize() {
  348.     fieldsToObj(this, this.searchLists);
  349.     if (this.skipDays)
  350.       this.skipDays = this.skipDays.getProperty("days");
  351.     if (this.skipHours)
  352.       this.skipHours = this.skipHours.getProperty("hours");
  353.  
  354.     if (this.updated)
  355.       this.updated = dateParse(this.updated);
  356.  
  357.     // Assign Atom link if needed
  358.     if (bagHasKey(this.fields, "links"))
  359.       this._atomLinksToURI();
  360.  
  361.     // Resolve relative image links
  362.     if (this.image && bagHasKey(this.image, "url")) {
  363.       this._resolveImageLink();
  364.     }
  365.  
  366.     this._resetBagMembersToRawText([this.searchLists.subtitle, 
  367.                                     this.searchLists.title]);
  368.   },
  369.  
  370.   _atomLinksToURI: function Feed_linkToURI() {
  371.     var links = this.fields.getPropertyAsInterface("links", Ci.nsIArray);
  372.     var alternates = findAtomLinks("alternate", links);
  373.     if (alternates.length > 0) {
  374.       var href = alternates[0].getPropertyAsAString("href");
  375.       var base;
  376.       if (bagHasKey(alternates[0], "xml:base"))
  377.         base = alternates[0].getPropertyAsAString("xml:base");
  378.       this.link = this._resolveURI(href, base);
  379.     }
  380.   },
  381.  
  382.   _resolveImageLink: function Feed_resolveImageLink() {
  383.     var base;
  384.     if (bagHasKey(this.image, "xml:base"))
  385.       base = this.image.getPropertyAsAString("xml:base");
  386.     var url = this._resolveURI(this.image.getPropertyAsAString("url"), base);
  387.     if (url)
  388.       this.image.setPropertyAsAString("url", url.spec);
  389.   },
  390.  
  391.   _resolveURI: function Feed_resolveURI(linkSpec, baseSpec) {
  392.     var uri = null;
  393.     try {
  394.       var base = baseSpec ? strToURI(baseSpec, this.baseURI) : this.baseURI;
  395.       uri = strToURI(linkSpec, base);
  396.     }
  397.     catch(e) {
  398.       LOG(e);
  399.     }
  400.  
  401.     return uri;
  402.   },
  403.  
  404.   // reset the bag to raw contents, not text constructs
  405.   _resetBagMembersToRawText: function Feed_resetBagMembers(fieldLists) {
  406.     for (var i=0; i<fieldLists.length; i++) {      
  407.       for (var j=0; j<fieldLists[i].length; j++) {
  408.         if (bagHasKey(this.fields, fieldLists[i][j])) {
  409.           var textConstruct = this.fields.getProperty(fieldLists[i][j]);
  410.           this.fields.setPropertyAsAString(fieldLists[i][j],
  411.                                            textConstruct.text);
  412.         }
  413.       }
  414.     }
  415.   },
  416.    
  417.   // flockIFeedContainer
  418.   setMetadataOnly: function Feed_setMetadataOnly(metadataOnly) {
  419.     this.metadataOnly = metadataOnly;
  420.   },
  421.  
  422.   QueryInterface: function Feed_QI(iid) {
  423.     if (iid.equals(Ci.nsIFeed) ||
  424.         iid.equals(Ci.flockIFeedContainer) ||
  425.         iid.equals(Ci.nsIFeedContainer) ||
  426.         iid.equals(Ci.nsISupports))
  427.     return this;
  428.     throw Cr.NS_ERROR_NOINTERFACE;
  429.   }
  430. }
  431.  
  432. function Entry() {
  433.   this.summary = null;
  434.   this.content = null;
  435.   this.title = null;
  436.   this.fields = Cc["@mozilla.org/hash-property-bag;1"].
  437.     createInstance(Ci.nsIWritablePropertyBag2);
  438.   this.link = null;
  439.   this.id = null;
  440.   this.baseURI = null;
  441.   this.updated = null;
  442.   this.published = null;
  443.   this.authors = Cc[ARRAY_CONTRACTID].createInstance(Ci.nsIMutableArray);
  444.   this.contributors = Cc[ARRAY_CONTRACTID].createInstance(Ci.nsIMutableArray);
  445. }
  446.   
  447. Entry.prototype = {
  448.   fields: null,
  449.   enclosures: null,
  450.   mediaContent: null,
  451.   
  452.   searchLists: {
  453.     title: ["title", "rss1:title", "atom03:title", "atom:title"],
  454.     link: [["link",strToURI],["rss1:link",strToURI]],
  455.     id: [["guid", makePropGetter("guid")], "rdf:about",
  456.          "atom03:id", "atom:id"],
  457.     authors : ["authors"],
  458.     contributors: ["contributors"],
  459.     summary: ["description", "rss1:description", "dc:description",
  460.               "atom03:summary", "atom:summary"],
  461.     content: ["content:encoded","atom03:content","atom:content"],
  462.     rights: ["atom03:rights","atom:rights"],
  463.     published: ["atom03:issued", "dcterms:issued", "atom:published"],
  464.     updated: ["pubDate", "atom03:modified", "dc:date", "dcterms:modified",
  465.               "atom:updated"]
  466.   },
  467.   
  468.   normalize: function Entry_normalize() {
  469.     fieldsToObj(this, this.searchLists);
  470.  
  471.     // Assign Atom link if needed
  472.     if (bagHasKey(this.fields, "links"))
  473.       this._atomLinksToURI();
  474.  
  475.     // The link might be a guid w/ permalink=true
  476.     if (!this.link && bagHasKey(this.fields, "guid")) {
  477.       var guid = this.fields.getProperty("guid");
  478.       var isPermaLink = true;
  479.       
  480.       if (bagHasKey(guid, "isPermaLink"))
  481.         isPermaLink = guid.getProperty("isPermaLink").toLowerCase() != "false";
  482.       
  483.       if (guid && isPermaLink)
  484.         this.link = strToURI(guid.getProperty("guid"));
  485.     }
  486.  
  487.     if (this.updated)
  488.       this.updated = dateParse(this.updated);
  489.     if (this.published)
  490.       this.published = dateParse(this.published);
  491.  
  492.     this._resetBagMembersToRawText([this.searchLists.content, 
  493.                                     this.searchLists.summary, 
  494.                                     this.searchLists.title]);
  495.   },
  496.   
  497.   QueryInterface: function(iid) {
  498.     if (iid.equals(Ci.nsIFeedEntry) ||
  499.         iid.equals(Ci.nsIFeedContainer) ||
  500.         iid.equals(Ci.nsISupports))
  501.     return this;
  502.  
  503.     throw Cr.NS_ERROR_NOINTERFACE;
  504.   }
  505. }
  506.  
  507. Entry.prototype._atomLinksToURI = Feed.prototype._atomLinksToURI;
  508. Entry.prototype._resolveURI = Feed.prototype._resolveURI;
  509. Entry.prototype._resetBagMembersToRawText = 
  510.    Feed.prototype._resetBagMembersToRawText;
  511.  
  512. // TextConstruct represents and element that could contain (X)HTML
  513. function TextConstruct() {
  514.   this.lang = null;
  515.   this.base = null;
  516.   this.type = "text";
  517.   this.text = null;
  518. }
  519.  
  520. TextConstruct.prototype = {
  521.   plainText: function TC_plainText() {
  522.     if (this.type != "text") {
  523.       if (this.text)
  524.         return gUnescapeHTML.unescape(stripTags(this.text));
  525.       else
  526.         return '';
  527.     }
  528.     return this.text;
  529.   },
  530.  
  531.   createDocumentFragment: function TC_createDocumentFragment(element) {
  532.     if (this.type == "text") {
  533.       var doc = element.ownerDocument;
  534.       var docFragment = doc.createDocumentFragment();
  535.       var node = doc.createTextNode(this.text);
  536.       docFragment.appendChild(node);
  537.       return docFragment;
  538.     }
  539.     var isXML;
  540.     if (this.type == "xhtml")
  541.       isXML = true
  542.     else if (this.type == "html")
  543.       isXML = false;
  544.     else
  545.       return null;
  546.  
  547.     return gUnescapeHTML.parseFragment(this.text, isXML, this.base, element);
  548.   },
  549.  
  550.   QueryInterface: function(iid) {
  551.     if (iid.equals(Ci.nsIFeedTextConstruct) ||
  552.         iid.equals(Ci.nsISupports))
  553.     return this;
  554.  
  555.     throw Cr.NS_ERROR_NOINTERFACE;
  556.   }
  557. }
  558.  
  559. // Generator represents the software that produced the feed
  560. function Generator() {
  561.   this.lang = null;
  562.   this.agent = null;
  563.   this.version = null;
  564.   this.uri = null;
  565.  
  566.   // nsIFeedElementBase
  567.   this._attributes = null;
  568.   this.baseURI = null;
  569. }
  570.  
  571. Generator.prototype = {
  572.  
  573.   get attributes() {
  574.     return this._attributes;
  575.   },
  576.  
  577.   set attributes(value) {
  578.     this._attributes = value;
  579.     this.version = this._attributes.getValueFromName("","version");
  580.     var uriAttribute = this._attributes.getValueFromName("","uri") ||
  581.                        this._attributes.getValueFromName("","url");
  582.     this.uri = strToURI(uriAttribute, this.baseURI);
  583.  
  584.     // RSS1
  585.     uriAttribute = this._attributes.getValueFromName(RDF_NS,"resource");
  586.     if (uriAttribute) {
  587.       this.agent = uriAttribute;
  588.       this.uri = strToURI(uriAttribute, this.baseURI);
  589.     }
  590.   },
  591.  
  592.   QueryInterface: function(iid) {
  593.     if (iid.equals(Ci.nsIFeedGenerator) ||
  594.         iid.equals(Ci.nsIFeedElementBase) ||
  595.         iid.equals(Ci.nsISupports))
  596.     return this;
  597.  
  598.     throw Cr.NS_ERROR_NOINTERFACE;
  599.   }
  600. }
  601.  
  602. function Person() {
  603.   this.name = null;
  604.   this.uri = null;
  605.   this.email = null;
  606.  
  607.   // nsIFeedElementBase
  608.   this.attributes = null;
  609.   this.baseURI = null;
  610. }
  611.  
  612. Person.prototype = {
  613.   QueryInterface: function(iid) {
  614.     if (iid.equals(Ci.nsIFeedPerson) ||
  615.         iid.equals(Ci.nsIFeedElementBase) ||
  616.         iid.equals(Ci.nsISupports))
  617.     return this;
  618.  
  619.     throw Cr.NS_ERROR_NOINTERFACE;
  620.   }
  621. }
  622.  
  623. /** 
  624.  * Map a list of fields into properties on a container.
  625.  *
  626.  * @param container An nsIFeedContainer
  627.  * @param fields A list of fields to search for. List members can
  628.  *               be a list, in which case the second member is 
  629.  *               transformation function (like parseInt).
  630.  */
  631. function fieldsToObj(container, fields) {
  632.   var props,prop,field,searchList;
  633.   for (var key in fields) {
  634.     searchList = fields[key];
  635.     for (var i=0; i < searchList.length; ++i) {
  636.       props = searchList[i];
  637.       prop = null;
  638.       field = isArray(props) ? props[0] : props;
  639.       try {
  640.         prop = container.fields.getProperty(field);
  641.       } 
  642.       catch(e) { 
  643.       }
  644.       if (prop) {
  645.         prop = isArray(props) ? props[1](prop) : prop;
  646.         container[key] = prop;
  647.       }
  648.     }
  649.   }
  650. }
  651.  
  652. /**
  653.  * Lower cases an element's localName property
  654.  * @param   element A DOM element.
  655.  *
  656.  * @returns The lower case localName property of the specified element
  657.  */
  658. function LC(element) {
  659.   return element.localName.toLowerCase();
  660. }
  661.  
  662. // TODO move these post-processor functions
  663. // create a generator element
  664. function atomGenerator(s, generator) {
  665.   generator.QueryInterface(Ci.nsIFeedGenerator);
  666.   generator.agent = trimString(s);
  667.   return generator;
  668. }
  669.  
  670. // post-process atom:logo to create an RSS2-like structure
  671. function atomLogo(s, logo) {
  672.   logo.setPropertyAsAString("url", trimString(s));
  673. }
  674.  
  675. // post-process an RSS category, map it to the Atom fields.
  676. function rssCatTerm(s, cat) {
  677.   // add slash handling?
  678.   cat.setPropertyAsAString("term", trimString(s));
  679.   return cat;
  680.  
  681. // post-process a GUID 
  682. function rssGuid(s, guid) {
  683.   guid.setPropertyAsAString("guid", trimString(s));
  684.   return guid;
  685. }
  686.  
  687. // post-process an RSS author element
  688. //
  689. // It can contain a field like this:
  690. // 
  691. //  <author>lawyer@boyer.net (Lawyer Boyer)</author>
  692. //
  693. // or, delightfully, a field like this:
  694. //
  695. //  <dc:creator>Simon St.Laurent (mailto:simonstl@simonstl.com)</dc:creator>
  696. //
  697. // We want to split this up and assign it to corresponding Atom
  698. // fields.
  699. //
  700. function rssAuthor(s,author) {
  701.   author.QueryInterface(Ci.nsIFeedPerson);
  702.   // check for RSS2 string format
  703.   var chars = trimString(s);
  704.   var matches = chars.match(/(.*)\((.*)\)/);
  705.   var emailCheck = 
  706.     /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;
  707.   if (matches) {
  708.     var match1 = trimString(matches[1]);
  709.     var match2 = trimString(matches[2]);
  710.     if (match2.indexOf("mailto:") == 0)
  711.       match2 = match2.substring(7);
  712.     if (emailCheck.test(match1)) {
  713.       author.email = match1;
  714.       author.name = match2;
  715.     }
  716.     else if (emailCheck.test(match2)) {
  717.       author.email = match2;
  718.       author.name = match1;
  719.     }
  720.     else {
  721.       // put it back together
  722.       author.name = match1 + " (" + match2 + ")";
  723.     }
  724.   }
  725.   else {
  726.     author.name = chars;
  727.     if (chars.indexOf('@'))
  728.       author.email = chars;
  729.   }
  730.   return author;
  731. }
  732.  
  733. //
  734. // skipHours and skipDays map to arrays, so we need to change the
  735. // string to an nsISupports in order to stick it in there.
  736. //
  737. function rssArrayElement(s) {
  738.   var str = Cc["@mozilla.org/supports-string;1"].
  739.               createInstance(Ci.nsISupportsString);
  740.   str.data = s;
  741.   str.QueryInterface(Ci.nsISupportsString);
  742.   return str;
  743. }
  744.  
  745. /***** Some feed utils from TBird *****/
  746.  
  747. /**
  748.  * Tests a RFC822 date against a regex.
  749.  * @param aDateStr A string to test as an RFC822 date.
  750.  *
  751.  * @returns A boolean indicating whether the string is a valid RFC822 date.
  752.  */
  753. function isValidRFC822Date(aDateStr) {
  754.   var regex = new RegExp(RFC822_RE);
  755.   return regex.test(aDateStr);
  756. }
  757.  
  758. /**
  759.  * Removes leading and trailing whitespace from a string.
  760.  * @param s The string to trim.
  761.  *
  762.  * @returns A new string with whitespace stripped.
  763.  */
  764. function trimString(s) {
  765.   return(s.replace(/^\s+/, "").replace(/\s+$/, ""));
  766. }
  767.  
  768. // Regular expression matching RFC822 dates 
  769. const RFC822_RE = "^(((Mon)|(Tue)|(Wed)|(Thu)|(Fri)|(Sat)|(Sun)), *)?\\d\\d?"
  770. + " +((Jan)|(Feb)|(Mar)|(Apr)|(May)|(Jun)|(Jul)|(Aug)|(Sep)|(Oct)|(Nov)|(Dec))"
  771. + " +\\d\\d(\\d\\d)? +\\d\\d:\\d\\d(:\\d\\d)? +(([+-]?\\d\\d\\d\\d)|(UT)|(GMT)"
  772. + "|(EST)|(EDT)|(CST)|(CDT)|(MST)|(MDT)|(PST)|(PDT)|\\w)$";
  773.  
  774. /**
  775.  * XXX -- need to decide what this should return. 
  776.  * XXX -- Is there a Date class usable from C++?
  777.  *
  778.  * Tries tries parsing various date formats.
  779.  * @param dateString
  780.  *        A string that is supposedly an RFC822 or RFC3339 date.
  781.  * @returns A Date.toString XXX--fixme
  782.  */
  783. function dateParse(dateString) {
  784.   var date = trimString(dateString);
  785.  
  786.   if (date.search(/^\d\d\d\d/) != -1) //Could be a ISO8601/W3C date
  787.     return W3CToIETFDate(dateString);
  788.  
  789.   if (isValidRFC822Date(date))
  790.     return date; 
  791.   
  792.   if (!isNaN(parseInt(date, 10))) { 
  793.     //It's an integer, so maybe it's a timestamp
  794.     var d = new Date(parseInt(date, 10) * 1000);
  795.     var now = new Date();
  796.     var yeardiff = now.getFullYear() - d.getFullYear();
  797.     if ((yeardiff >= 0) && (yeardiff < 3)) {
  798.       // it's quite likely the correct date. 3 years is an arbitrary cutoff,
  799.       // but this is an invalid date format, and there's no way to verify
  800.       // its correctness.
  801.       return d.toString();
  802.     }
  803.   }
  804.   // Can't help.
  805.   return null;
  806.  
  807.  
  808. const XHTML_NS = "http://www.w3.org/1999/xhtml";
  809.  
  810. // The XHTMLHandler handles inline XHTML found in things like atom:summary
  811. function XHTMLHandler(processor, isAtom) {
  812.   this._buf = "";
  813.   this._processor = processor;
  814.   this._depth = 0;
  815.   this._isAtom = isAtom;
  816. }
  817.  
  818. // The fidelity can be improved here, to allow handling of stuff like
  819. // SVG and MathML. XXX
  820. XHTMLHandler.prototype = {
  821.   startDocument: function XH_startDocument() {
  822.   },
  823.   endDocument: function XH_endDocument() {
  824.   },
  825.   startElement: function XH_startElement(uri, localName, qName, attributes) {
  826.     ++this._depth;
  827.  
  828.     // RFC4287 requires XHTML to be wrapped in a div that is *not* part of 
  829.     // the content. This prevents people from screwing up namespaces, but
  830.     // we need to skip it here.
  831.     if (this._isAtom && this._depth == 1 && localName == "div")
  832.       return;
  833.  
  834.     // If it's an XHTML element, record it. Otherwise, it's ignored.
  835.     if (uri == XHTML_NS) {
  836.       this._buf += "<" + localName;
  837.       for (var i=0; i < attributes.length; ++i) {
  838.         // XHTML attributes aren't in a namespace
  839.         if (attributes.getURI(i) == "") { 
  840.           this._buf += (" " + attributes.getLocalName(i) + "='" +
  841.                         xmlEscape(attributes.getValue(i)) + "'");
  842.         }
  843.       }
  844.       this._buf += ">";
  845.     }
  846.   },
  847.   endElement: function XH_endElement(uri, localName, qName) {
  848.     --this._depth;
  849.     
  850.     // We need to skip outer divs in Atom. See comment in startElement.
  851.     if (this._isAtom && this._depth == 0 && localName == "div")
  852.       return;
  853.  
  854.     // When we peek too far, go back to the main processor
  855.     if (this._depth < 0) {
  856.       this._processor.returnFromXHTMLHandler(trimString(this._buf),
  857.                                              uri, localName, qName);
  858.       return;
  859.     }
  860.     // If it's an XHTML element, record it. Otherwise, it's ignored.
  861.     if (uri == XHTML_NS) {
  862.       this._buf += "</" + localName + ">";
  863.     }
  864.   },
  865.   characters: function XH_characters(data) {
  866.     this._buf += xmlEscape(data);
  867.   },
  868.   startPrefixMapping: function XH_startPrefixMapping() {
  869.   },
  870.   endPrefixMapping: function XH_endPrefixMapping() {
  871.   },
  872.   processingInstruction: function XH_processingInstruction() {
  873.   }, 
  874. }
  875.  
  876. /**
  877.  * The ExtensionHandler deals with elements we haven't explicitly
  878.  * added to our transition table in the FeedProcessor.
  879.  */
  880. function ExtensionHandler(processor) {
  881.   this._buf = "";
  882.   this._depth = 0;
  883.   this._hasChildElements = false;
  884.  
  885.   // The FeedProcessor
  886.   this._processor = processor;
  887.  
  888.   // Fields of the outermost extension element.
  889.   this._localName = null;
  890.   this._uri = null;
  891.   this._qName = null;
  892.   this._attrs = null;
  893. }
  894.  
  895. ExtensionHandler.prototype = {
  896.   startDocument: function EH_startDocument() {
  897.   },
  898.   endDocument: function EH_endDocument() {
  899.   },
  900.   startElement: function EH_startElement(uri, localName, qName, attrs) {
  901.     ++this._depth;
  902.     var prefix = gNamespaces[uri] ? gNamespaces[uri] + ":" : "";
  903.     var key =  prefix + localName;
  904.     
  905.     if (this._depth == 1) {
  906.       this._uri = uri;
  907.       this._localName = localName;
  908.       this._qName = qName;
  909.       this._attrs = attrs;
  910.     }
  911.     
  912.     // if we descend into another element, we won't send text
  913.     this._hasChildElements = (this._depth > 1);
  914.     
  915.   },
  916.   endElement: function EH_endElement(uri, localName, qName) {
  917.     --this._depth;
  918.     if (this._depth == 0) {
  919.       var text = this._hasChildElements ? null : trimString(this._buf);
  920.       this._processor.returnFromExtHandler(this._uri, this._localName, 
  921.                                            text, this._attrs);
  922.     }
  923.   },
  924.   characters: function EH_characters(data) {
  925.     if (!this._hasChildElements)
  926.       this._buf += data;
  927.   },
  928.   startPrefixMapping: function EH_startPrefixMapping() {
  929.   },
  930.   endPrefixMapping: function EH_endPrefixMapping() {
  931.   },
  932.   processingInstruction: function EH_processingInstruction() {
  933.   }, 
  934. };
  935.  
  936.  
  937. /**
  938.  * ElementInfo is a simple container object that describes
  939.  * some characteristics of a feed element. For example, it
  940.  * says whether an element can be expected to appear more
  941.  * than once inside a given entry or feed.
  942.  */ 
  943. function ElementInfo(fieldName, containerClass, closeFunc, isArray) {
  944.   this.fieldName = fieldName;
  945.   this.containerClass = containerClass;
  946.   this.closeFunc = closeFunc;
  947.   this.isArray = isArray;
  948.   this.isWrapper = false;
  949. }
  950.  
  951. /**
  952.  * FeedElementInfo represents a feed element, usually the root.
  953.  */
  954. function FeedElementInfo(fieldName, feedVersion) {
  955.   this.isWrapper = false;
  956.   this.fieldName = fieldName;
  957.   this.feedVersion = feedVersion;
  958. }
  959.  
  960. /**
  961.  * Some feed formats include vestigial wrapper elements that we don't
  962.  * want to include in our object model, but we do need to keep track
  963.  * of during parsing.
  964.  */
  965. function WrapperElementInfo(fieldName) {
  966.   this.isWrapper = true;
  967.   this.fieldName = fieldName;
  968. }
  969.  
  970. /***** The Processor *****/
  971. function FeedProcessor() {
  972.   this._reader = Cc[SAX_CONTRACTID].createInstance(Ci.nsISAXXMLReader);
  973.   this._buf =  "";
  974.   this._feed = Cc[BAG_CONTRACTID].createInstance(Ci.nsIWritablePropertyBag2);
  975.   this._handlerStack = [];
  976.   this._xmlBaseStack = []; // sparse array keyed to nesting depth
  977.   this._depth = 0;
  978.   this._state = "START";
  979.   this._result = null;
  980.   this._extensionHandler = null;
  981.   this._xhtmlHandler = null;
  982.  
  983.   // The nsIFeedResultListener waiting for the parse results
  984.   this.listener = null;
  985.  
  986.   // These elements can contain (X)HTML or plain text.
  987.   // We keep a table here that contains their default treatment
  988.   this._textConstructs = {"atom:title":"text",
  989.                           "atom:summary":"text",
  990.                           "atom:rights":"text",
  991.                           "atom:content":"text",
  992.                           "atom:subtitle":"text",
  993.                           "description":"html",
  994.                           "rss1:description":"html",
  995.                           "dc:description":"html",
  996.                           "content:encoded":"html",
  997.                           "title":"text",
  998.                           "rss1:title":"text",
  999.                           "atom03:title":"text",
  1000.                           "atom03:tagline":"text",
  1001.                           "atom03:summary":"text",
  1002.                           "atom03:content":"text"};
  1003.   this._stack = [];
  1004.  
  1005.   this._trans = {   
  1006.     "START": {
  1007.       //If we hit a root RSS element, treat as RSS2.
  1008.       "rss": new FeedElementInfo("RSS2", "rss2"),
  1009.  
  1010.       // If we hit an RDF element, if could be RSS1, but we can't
  1011.       // verify that until we hit a rss1:channel element.
  1012.       "rdf:RDF": new WrapperElementInfo("RDF"),
  1013.  
  1014.       // If we hit a Atom 1.0 element, treat as Atom 1.0.
  1015.       "atom:feed": new FeedElementInfo("Atom", "atom"),
  1016.  
  1017.       // Treat as Atom 0.3
  1018.       "atom03:feed": new FeedElementInfo("Atom03", "atom03"),
  1019.     },
  1020.     
  1021.     /********* RSS2 **********/
  1022.     "IN_RSS2": {
  1023.       "channel": new WrapperElementInfo("channel")
  1024.     },
  1025.  
  1026.     "IN_CHANNEL": {
  1027.       "item": new ElementInfo("items", Cc[ENTRY_CONTRACTID], null, true),
  1028.       "managingEditor": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
  1029.                                         rssAuthor, true),
  1030.       "dc:creator": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
  1031.                                     rssAuthor, true),
  1032.       "dc:author": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
  1033.                                    rssAuthor, true),
  1034.       "dc:contributor": new ElementInfo("contributors", Cc[PERSON_CONTRACTID],
  1035.                                          rssAuthor, true),
  1036.       "category": new ElementInfo("categories", null, rssCatTerm, true),
  1037.       "cloud": new ElementInfo("cloud", null, null, false),
  1038.       "image": new ElementInfo("image", null, null, false),
  1039.       "textInput": new ElementInfo("textInput", null, null, false),
  1040.       "skipDays": new ElementInfo("skipDays", null, null, false),
  1041.       "skipHours": new ElementInfo("skipHours", null, null, false),
  1042.       "generator": new ElementInfo("generator", Cc[GENERATOR_CONTRACTID],
  1043.                                    atomGenerator, false),
  1044.     },
  1045.  
  1046.     "IN_ITEMS": {
  1047.       "author": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
  1048.                                 rssAuthor, true),
  1049.       "dc:creator": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
  1050.                                     rssAuthor, true),
  1051.       "dc:author": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
  1052.                                    rssAuthor, true),
  1053.       "dc:contributor": new ElementInfo("contributors", Cc[PERSON_CONTRACTID],
  1054.                                          rssAuthor, true),
  1055.       "category": new ElementInfo("categories", null, rssCatTerm, true),
  1056.       "enclosure": new ElementInfo("enclosure", null, null, true),
  1057.       "guid": new ElementInfo("guid", null, rssGuid, false)
  1058.     },
  1059.  
  1060.     "IN_SKIPDAYS": {
  1061.       "day": new ElementInfo("days", null, rssArrayElement, true)
  1062.     },
  1063.  
  1064.     "IN_SKIPHOURS":{
  1065.       "hour": new ElementInfo("hours", null, rssArrayElement, true)
  1066.     },
  1067.  
  1068.     /********* RSS1 **********/
  1069.     "IN_RDF": {
  1070.       // If we hit a rss1:channel, we can verify that we have RSS1
  1071.       "rss1:channel": new FeedElementInfo("rdf_channel", "rss1"),
  1072.       "rss1:image": new ElementInfo("image", null, null, false),
  1073.       "rss1:textinput": new ElementInfo("textInput", null, null, false),
  1074.       "rss1:item": new ElementInfo("items", Cc[ENTRY_CONTRACTID], null, true),
  1075.     },
  1076.  
  1077.     "IN_RDF_CHANNEL": {
  1078.       "admin:generatorAgent": new ElementInfo("generator",
  1079.                                               Cc[GENERATOR_CONTRACTID],
  1080.                                               null, false),
  1081.       "dc:creator": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
  1082.                                     rssAuthor, true),
  1083.       "dc:author": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
  1084.                                    rssAuthor, true),
  1085.       "dc:contributor": new ElementInfo("contributors", Cc[PERSON_CONTRACTID],
  1086.                                          rssAuthor, true),
  1087.     },
  1088.  
  1089.     /********* ATOM 1.0 **********/
  1090.     "IN_ATOM": {
  1091.       "atom:author": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
  1092.                                      null, true),
  1093.       "atom:generator": new ElementInfo("generator", Cc[GENERATOR_CONTRACTID],
  1094.                                         atomGenerator, false),
  1095.       "atom:contributor": new ElementInfo("contributors",  Cc[PERSON_CONTRACTID],
  1096.                                           null, true),
  1097.       "atom:link": new ElementInfo("links", null, null, true),
  1098.       "atom:logo": new ElementInfo("atom:logo", null, atomLogo, false),
  1099.       "atom:entry": new ElementInfo("entries", Cc[ENTRY_CONTRACTID],
  1100.                                     null, true)
  1101.     },
  1102.  
  1103.     "IN_ENTRIES": {
  1104.       "atom:author": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
  1105.                                      null, true),
  1106.       "atom:contributor": new ElementInfo("contributors", Cc[PERSON_CONTRACTID],
  1107.                                           null, true),
  1108.       "atom:link": new ElementInfo("links", null, null, true),
  1109.     },
  1110.  
  1111.     /********* ATOM 0.3 **********/
  1112.     "IN_ATOM03": {
  1113.       "atom03:author": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
  1114.                                        null, true),
  1115.       "atom03:contributor": new ElementInfo("contributors",
  1116.                                             Cc[PERSON_CONTRACTID],
  1117.                                             null, true),
  1118.       "atom03:link": new ElementInfo("links", null, null, true),
  1119.       "atom03:entry": new ElementInfo("atom03_entries", Cc[ENTRY_CONTRACTID],
  1120.                                       null, true),
  1121.       "atom03:generator": new ElementInfo("generator", Cc[GENERATOR_CONTRACTID],
  1122.                                           atomGenerator, false),
  1123.     },
  1124.  
  1125.     "IN_ATOM03_ENTRIES": {
  1126.       "atom03:author": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
  1127.                                        null, true),
  1128.       "atom03:contributor": new ElementInfo("contributors",
  1129.                                             Cc[PERSON_CONTRACTID],
  1130.                                             null, true),
  1131.       "atom03:link": new ElementInfo("links", null, null, true),
  1132.       "atom03:entry": new ElementInfo("atom03_entries", Cc[ENTRY_CONTRACTID],
  1133.                                       null, true)
  1134.     }
  1135.   }
  1136. }
  1137.  
  1138. // See startElement for a long description of how feeds are processed.
  1139. FeedProcessor.prototype = { 
  1140.   
  1141.   // Set ourselves as the SAX handler, and set the base URI
  1142.   _init: function FP_init(uri, metadataOnly) {
  1143.     this._reader.contentHandler = this;
  1144.     this._reader.errorHandler = this;
  1145.     this._result = Cc[FR_CONTRACTID].createInstance(Ci.nsIFeedResult);
  1146.     this._metadataOnly = metadataOnly;
  1147.     this._sentResult = false;
  1148.     if (uri) {
  1149.       this._result.uri = uri;
  1150.       this._reader.baseURI = uri;
  1151.       this._xmlBaseStack[0] = uri;
  1152.     }
  1153.   },
  1154.  
  1155.   // This function is called once we figure out what type of feed
  1156.   // we're dealing with. Some feed types require digging a bit further
  1157.   // than the root.
  1158.   _docVerified: function FP_docVerified(version) {
  1159.     this._result.doc = Cc[FEED_CONTRACTID].createInstance(Ci.nsIFeed);
  1160.     this._result.doc.baseURI = 
  1161.       this._xmlBaseStack[this._xmlBaseStack.length - 1];
  1162.     this._result.doc.fields = this._feed;
  1163.     this._result.version = version;
  1164.   },
  1165.  
  1166.   // When we're done with the feed, let the listener know what
  1167.   // happened.
  1168.   _sendResult: function FP_sendResult(cleanup) {
  1169.     if (!this._sentResult) {
  1170.       var feed = this._result.doc;
  1171.  
  1172.       try {
  1173.         // Can be null when a non-feed is fed to us
  1174.         if (feed)
  1175.           feed.normalize();
  1176.       }
  1177.       catch (e) {
  1178.         LOG("FIXME: " + e);
  1179.       }
  1180.  
  1181.       if (feed && this._metadataOnly) {
  1182.         feed.items.clear();
  1183.  
  1184.         var flockFeed = feed.QueryInterface(Ci.flockIFeedContainer);
  1185.         flockFeed.setMetadataOnly(true);
  1186.       }
  1187.  
  1188.       try {
  1189.         if (this.listener != null)
  1190.           this.listener.handleResult(this._result);
  1191.       }
  1192.       catch (e) { }
  1193.     }
  1194.  
  1195.     this._sentResult = true;
  1196.  
  1197.     if (cleanup) {
  1198.       this._result = null;
  1199.       this._reader = null;
  1200.     }
  1201.   },
  1202.  
  1203.   _maybeSendMetadata: function FP_maybeSendMetadata(request) {
  1204.     var dataReady = false;
  1205.  
  1206.     if (!this._result)
  1207.       return;
  1208.  
  1209.     var feed = this._result.doc;
  1210.  
  1211.     try {
  1212.       if (feed) {
  1213.         var searchList = ["title","rss1:title", "atom03:title","atom:title"];
  1214.         for each (var field in searchList) {
  1215.           var prop = null;
  1216.           try {
  1217.             prop = feed.fields.getProperty(field);
  1218.           }
  1219.           catch (e) { }
  1220.           if (prop) {
  1221.             dataReady = true;
  1222.             break;
  1223.           }
  1224.         }
  1225.       }
  1226.     }
  1227.     catch (e) {
  1228.       LOG("FIXME: " + e);
  1229.     }
  1230.  
  1231.     if (dataReady) {
  1232.       this._sendResult(false);
  1233.       request.cancel(NS_BINDING_ABORTED);
  1234.     }
  1235.   },
  1236.  
  1237.   // Parsing functions
  1238.   parseFromStream: function FP_parseFromStream(stream, uri) {
  1239.     this._init(uri, false);
  1240.     this._reader.parseFromStream(stream, null, stream.available(), 
  1241.                                  "application/xml");
  1242.     this._reader = null;
  1243.   },
  1244.  
  1245.   parseFromString: function FP_parseFromString(inputString, uri) {
  1246.     this._init(uri, false);
  1247.     this._reader.parseFromString(inputString, "application/xml");
  1248.     this._reader = null;
  1249.   },
  1250.  
  1251.   parseAsync: function FP_parseAsync(requestObserver, uri) {
  1252.     this._init(uri, false);
  1253.     this._reader.parseAsync(requestObserver);
  1254.   },
  1255.  
  1256.   // flockIFeedProcessor
  1257.   parseFeedMetadataAsync: function FP_parseFeedMetadataAsync(requestObserver, uri) {
  1258.     this._init(uri, true);
  1259.     this._reader.parseAsync(requestObserver);
  1260.   },
  1261.  
  1262.   // nsIStreamListener 
  1263.  
  1264.   // The XMLReader will throw sensible exceptions if these get called
  1265.   // out of order.
  1266.   onStartRequest: function FP_onStartRequest(request, context) {
  1267.     this._reader.onStartRequest(request, context);
  1268.   },
  1269.  
  1270.   onStopRequest: function FP_onStopRequest(request, context, statusCode) {
  1271.     this._reader.onStopRequest(request, context, statusCode);
  1272.   },
  1273.  
  1274.   onDataAvailable:
  1275.   function FP_onDataAvailable(request, context, inputStream, offset, count) {
  1276.     this._reader.onDataAvailable(request, context, inputStream, offset, count);
  1277.  
  1278.     if (this._metadataOnly)
  1279.       this._maybeSendMetadata(request);
  1280.   },
  1281.  
  1282.   // nsISAXErrorHandler
  1283.  
  1284.   // We only care about fatal errors. When this happens, we may have
  1285.   // parsed through the feed metadata and some number of entries. The
  1286.   // listener can still show some of that data if it wants, and we'll
  1287.   // set the bozo bit to indicate we were unable to parse all the way
  1288.   // through.
  1289.   fatalError: function FP_reportError() {
  1290.     this._result.bozo = true;
  1291.     //XXX need to QI to FeedProgressListener
  1292.     this._sendResult(true);
  1293.   },
  1294.  
  1295.   // nsISAXContentHandler
  1296.  
  1297.   startDocument: function FP_startDocument() {
  1298.     //LOG("----------");
  1299.   },
  1300.  
  1301.   endDocument: function FP_endDocument() {
  1302.     this._sendResult(true);
  1303.   },
  1304.  
  1305.   // The transitions defined above identify elements that contain more
  1306.   // than just text. For example RSS items contain many fields, and so
  1307.   // do Atom authors. The only commonly used elements that contain
  1308.   // mixed content are Atom Text Constructs of type="xhtml", which we
  1309.   // delegate to another handler for cleaning. That leaves a couple
  1310.   // different types of elements to deal with: those that should occur
  1311.   // only once, such as title elements, and those that can occur
  1312.   // multiple times, such as the RSS category element and the Atom
  1313.   // link element. Most of the RSS1/DC elements can occur multiple
  1314.   // times in theory, but in practice, the only ones that do have
  1315.   // analogues in Atom. 
  1316.   //
  1317.   // Some elements are also groups of attributes or sub-elements,
  1318.   // while others are simple text fields. For the most part, we don't
  1319.   // have to pay explicit attention to the simple text elements,
  1320.   // unless we want to post-process the resulting string to transform
  1321.   // it into some richer object like a Date or URI.
  1322.   //
  1323.   // Elements that have more sophisticated content models still end up
  1324.   // being dictionaries, whether they are based on attributes like RSS
  1325.   // cloud, sub-elements like Atom author, or even items and
  1326.   // entries. These elements are treated as "containers". It's
  1327.   // theoretically possible for a container to have an attribute with 
  1328.   // the same universal name as a sub-element, but none of the feed
  1329.   // formats allow this by default, and I don't of any extension that
  1330.   // works this way.
  1331.   //
  1332.   startElement: function FP_startElement(uri, localName, qName, attributes) {
  1333.     this._buf = "";
  1334.     ++this._depth;
  1335.     var elementInfo;
  1336.  
  1337.     //LOG("<" + localName + ">");
  1338.  
  1339.     // Check for xml:base
  1340.     var base = attributes.getValueFromName(XMLNS, "base");
  1341.     if (base) {
  1342.       this._xmlBaseStack[this._depth] =
  1343.         strToURI(base, this._xmlBaseStack[this._xmlBaseStack.length - 1]);
  1344.     }
  1345.  
  1346.     // To identify the element we're dealing with, we look up the
  1347.     // namespace URI in our gNamespaces dictionary, which will give us
  1348.     // a "canonical" prefix for a namespace URI. For example, this
  1349.     // allows Dublin Core "creator" elements to be consistently mapped
  1350.     // to "dc:creator", for easy field access by consumer code. This
  1351.     // strategy also happens to shorten up our state table.
  1352.     var key =  this._prefixForNS(uri) + localName;
  1353.  
  1354.     // Check to see if we need to hand this off to our XHTML handler.
  1355.     // The elements we're dealing with will look like this:
  1356.     // 
  1357.     // <title type="xhtml">
  1358.     //   <div xmlns="http://www.w3.org/1999/xhtml">
  1359.     //     A title with <b>bold</b> and <i>italics</i>.
  1360.     //   </div>
  1361.     // </title>
  1362.     //
  1363.     // When it returns in returnFromXHTMLHandler, the handler should
  1364.     // give us back a string like this: 
  1365.     // 
  1366.     //    "A title with <b>bold</b> and <i>italics</i>."
  1367.     //
  1368.     // The Atom spec explicitly says the div is not part of the content,
  1369.     // and explicitly allows whitespace collapsing.
  1370.     // 
  1371.     if ((this._result.version == "atom" || this._result.version == "atom03") &&
  1372.         this._textConstructs[key] != null) {
  1373.       var type = attributes.getValueFromName("","type");
  1374.       if (type != null && type.indexOf("xhtml") >= 0) {
  1375.         this._xhtmlHandler = 
  1376.           new XHTMLHandler(this, (this._result.version == "atom"));
  1377.         this._reader.contentHandler = this._xhtmlHandler;
  1378.         return;
  1379.       }
  1380.     }
  1381.  
  1382.     // Check our current state, and see if that state has a defined
  1383.     // transition. For example, this._trans["atom:entry"]["atom:author"]
  1384.     // will have one, and it tells us to add an item to our authors array.
  1385.     if (this._trans[this._state] && this._trans[this._state][key]) {
  1386.       elementInfo = this._trans[this._state][key];
  1387.     }
  1388.     else {
  1389.       // If we don't have a transition, hand off to extension handler
  1390.       this._extensionHandler = new ExtensionHandler(this);
  1391.       this._reader.contentHandler = this._extensionHandler;
  1392.       this._extensionHandler.startElement(uri, localName, qName, attributes);
  1393.       return;
  1394.     }
  1395.       
  1396.     // This distinguishes wrappers like 'channel' from elements
  1397.     // we'd actually like to do something with (which will test true).
  1398.     this._handlerStack[this._depth] = elementInfo; 
  1399.     if (elementInfo.isWrapper) {
  1400.       this._state = "IN_" + elementInfo.fieldName.toUpperCase();
  1401.       this._stack.push([this._feed, this._state]);
  1402.     } 
  1403.     else if (elementInfo.feedVersion) {
  1404.       this._state = "IN_" + elementInfo.fieldName.toUpperCase();
  1405.  
  1406.       // Check for the older RSS2 variants
  1407.       if (elementInfo.feedVersion == "rss2")
  1408.         elementInfo.feedVersion = this._findRSSVersion(attributes);
  1409.       else if (uri == RSS090NS)
  1410.         elementInfo.feedVersion = "rss090";
  1411.  
  1412.       this._docVerified(elementInfo.feedVersion);
  1413.       this._stack.push([this._feed, this._state]);
  1414.       this._mapAttributes(this._feed, attributes);
  1415.     }
  1416.     else {
  1417.       this._state = this._processComplexElement(elementInfo, attributes);
  1418.     }
  1419.   },
  1420.  
  1421.   // In the endElement handler, we decrement the stack and look
  1422.   // for cleanup/transition functions to execute. The second part
  1423.   // of the state transition works as above in startElement, but
  1424.   // the state we're looking for is prefixed with an underscore
  1425.   // to distinguish endElement events from startElement events.
  1426.   endElement:  function FP_endElement(uri, localName, qName) {
  1427.     var elementInfo = this._handlerStack[this._depth];
  1428.     //LOG("</" + localName + ">");
  1429.     if (elementInfo && !elementInfo.isWrapper)
  1430.       this._closeComplexElement(elementInfo);
  1431.   
  1432.     // cut down xml:base context
  1433.     if (this._xmlBaseStack.length == this._depth + 1)
  1434.       this._xmlBaseStack = this._xmlBaseStack.slice(0, this._depth);
  1435.  
  1436.     // our new state is whatever is at the top of the stack now
  1437.     if (this._stack.length > 0)
  1438.       this._state = this._stack[this._stack.length - 1][1];
  1439.     this._handlerStack = this._handlerStack.slice(0, this._depth);
  1440.     --this._depth;
  1441.   },
  1442.  
  1443.   // Buffer up character data. The buffer is cleared with every
  1444.   // opening element.
  1445.   characters: function FP_characters(data) {
  1446.     this._buf += data;
  1447.   },
  1448.  
  1449.   // TODO: It would be nice to check new prefixes here, and if they
  1450.   // don't conflict with the ones we've defined, throw them in a 
  1451.   // dictionary to check.
  1452.   startPrefixMapping: function FP_startPrefixMapping() {
  1453.   },
  1454.   endPrefixMapping: function FP_endPrefixMapping() {
  1455.   },
  1456.   processingInstruction: function FP_processingInstruction(target, data) {
  1457.     if (target == "xml-stylesheet") {
  1458.       var hrefAttribute = data.match(/href=[\"\'](.*?)[\"\']/);
  1459.       if (hrefAttribute && hrefAttribute.length == 2) 
  1460.         this._result.stylesheet = gIoService.newURI(hrefAttribute[1], null,
  1461.                                                     this._result.uri);
  1462.     }
  1463.   },
  1464.  
  1465.   // end of nsISAXContentHandler
  1466.  
  1467.   // Handle our more complicated elements--those that contain
  1468.   // attributes and child elements.
  1469.   _processComplexElement:
  1470.   function FP__processComplexElement(elementInfo, attributes) {
  1471.     var obj, key, prefix;
  1472.  
  1473.     // If the container is an entry/item, it'll need to have its 
  1474.     // more esoteric properties put in the 'fields' property bag.
  1475.     if (elementInfo.containerClass == Cc[ENTRY_CONTRACTID]) {
  1476.       obj = elementInfo.containerClass.createInstance(Ci.nsIFeedEntry);
  1477.       obj.baseURI = this._xmlBaseStack[this._xmlBaseStack.length - 1];
  1478.       this._mapAttributes(obj.fields, attributes);
  1479.     }
  1480.     else if (elementInfo.containerClass) {
  1481.       obj = elementInfo.containerClass.createInstance(Ci.nsIFeedElementBase);
  1482.       obj.baseURI = this._xmlBaseStack[this._xmlBaseStack.length - 1];
  1483.       obj.attributes = attributes; // just set the SAX attributes
  1484.     }
  1485.     else {
  1486.       obj = Cc[BAG_CONTRACTID].createInstance(Ci.nsIWritablePropertyBag2);
  1487.       this._mapAttributes(obj, attributes);
  1488.     }
  1489.  
  1490.     // We should have a container/propertyBag that's had its
  1491.     // attributes processed. Now we need to attach it to its
  1492.     // container.
  1493.     var newProp;
  1494.  
  1495.     // First we'll see what's on top of the stack.
  1496.     var container = this._stack[this._stack.length - 1][0];
  1497.  
  1498.     // Check to see if it has the property
  1499.     var prop;
  1500.     try {
  1501.       prop = container.getProperty(elementInfo.fieldName);
  1502.     }
  1503.     catch(e) {
  1504.     }
  1505.     
  1506.     if (elementInfo.isArray) {
  1507.       if (!prop) {
  1508.         container.setPropertyAsInterface(elementInfo.fieldName,
  1509.                                          Cc[ARRAY_CONTRACTID].
  1510.                                          createInstance(Ci.nsIMutableArray));
  1511.       }
  1512.  
  1513.       newProp = container.getProperty(elementInfo.fieldName);
  1514.       // XXX This QI should not be necessary, but XPConnect seems to fly
  1515.       // off the handle in the browser, and loses track of the interface
  1516.       // on large files. Bug 335638.
  1517.       newProp.QueryInterface(Ci.nsIMutableArray);
  1518.       newProp.appendElement(obj,false);
  1519.       
  1520.       // If new object is an nsIFeedContainer, we want to deal with
  1521.       // its member nsIPropertyBag instead.
  1522.       if (isIFeedContainer(obj))
  1523.         newProp = obj.fields; 
  1524.  
  1525.     }
  1526.     else {
  1527.       // If it doesn't, set it.
  1528.       if (!prop) {
  1529.         container.setPropertyAsInterface(elementInfo.fieldName,obj);
  1530.       }
  1531.       newProp = container.getProperty(elementInfo.fieldName);
  1532.     }
  1533.     
  1534.     // make our new state name, and push the property onto the stack
  1535.     var newState = "IN_" + elementInfo.fieldName.toUpperCase();
  1536.     this._stack.push([newProp, newState, obj]);
  1537.     return newState;
  1538.   },
  1539.  
  1540.   // Sometimes we need reconcile the element content with the object
  1541.   // model for a given feed. We use helper functions to do the
  1542.   // munging, but we need to identify array types here, so the munging
  1543.   // happens only to the last element of an array.
  1544.   _closeComplexElement: function FP__closeComplexElement(elementInfo) {
  1545.     var stateTuple = this._stack.pop();
  1546.     var container = stateTuple[0];
  1547.     var containerParent = stateTuple[2];
  1548.     var element = null;
  1549.     var isArray = isIArray(container);
  1550.  
  1551.     // If it's an array and we have to post-process,
  1552.     // grab the last element
  1553.     if (isArray)
  1554.       element = container.queryElementAt(container.length - 1, Ci.nsISupports);
  1555.     else
  1556.       element = container;
  1557.  
  1558.     // Run the post-processing function if there is one.
  1559.     if (elementInfo.closeFunc)
  1560.       element = elementInfo.closeFunc(this._buf, element);
  1561.  
  1562.     // If an nsIFeedContainer was on top of the stack,
  1563.     // we need to normalize it
  1564.     if (elementInfo.containerClass == Cc[ENTRY_CONTRACTID])
  1565.       containerParent.normalize();
  1566.  
  1567.     // If it's an array, re-set the last element
  1568.     if (isArray)
  1569.       container.replaceElementAt(element, container.length - 1, false);
  1570.   },
  1571.   
  1572.   _prefixForNS: function FP_prefixForNS(uri) {
  1573.     if (!uri)
  1574.       return "";
  1575.     var prefix = gNamespaces[uri];
  1576.     if (prefix)
  1577.       return prefix + ":";
  1578.     if (uri.toLowerCase().indexOf("http://backend.userland.com") == 0)
  1579.       return "";
  1580.     else
  1581.       return null;
  1582.   },
  1583.  
  1584.   _mapAttributes: function FP__mapAttributes(bag, attributes) {
  1585.     // Cycle through the attributes, and set our properties using the
  1586.     // prefix:localNames we find in our namespace dictionary.
  1587.     for (var i = 0; i < attributes.length; ++i) {
  1588.       var key = this._prefixForNS(attributes.getURI(i)) + attributes.getLocalName(i);
  1589.       var val = attributes.getValue(i);
  1590.       bag.setPropertyAsAString(key, val);
  1591.     }
  1592.   },
  1593.  
  1594.   // Only for RSS2esque formats
  1595.   _findRSSVersion: function FP__findRSSVersion(attributes) {
  1596.     var versionAttr = trimString(attributes.getValueFromName("", "version"));
  1597.     var versions = { "0.91":"rss091",
  1598.                      "0.92":"rss092",
  1599.                      "0.93":"rss093",
  1600.                      "0.94":"rss094" }
  1601.     if (versions[versionAttr])
  1602.       return versions[versionAttr];
  1603.     if (versionAttr.substr(0,2) != "2.")
  1604.       return "rssUnknown";
  1605.     return "rss2";
  1606.   },
  1607.  
  1608.   // unknown element values are returned here. See startElement above
  1609.   // for how this works.
  1610.   returnFromExtHandler:
  1611.   function FP_returnExt(uri, localName, chars, attributes) {
  1612.     --this._depth;
  1613.  
  1614.     // take control of the SAX events
  1615.     this._reader.contentHandler = this;
  1616.     if (localName == null && chars == null)
  1617.       return;
  1618.  
  1619.     // we don't take random elements inside rdf:RDF
  1620.     if (this._state == "IN_RDF")
  1621.       return;
  1622.     
  1623.     // Grab the top of the stack
  1624.     var top = this._stack[this._stack.length - 1];
  1625.     if (!top) 
  1626.       return;
  1627.  
  1628.     var container = top[0];
  1629.     // Grab the last element if it's an array
  1630.     if (isIArray(container)) {
  1631.       var contract = this._handlerStack[this._depth].containerClass;
  1632.       // check if it's something specific, but not an entry
  1633.       if (contract && contract != Cc[ENTRY_CONTRACTID]) {
  1634.         var el = container.queryElementAt(container.length - 1, 
  1635.                                           Ci.nsIFeedElementBase);
  1636.         // XXX there must be a way to flatten these interfaces
  1637.         if (contract == Cc[PERSON_CONTRACTID]) 
  1638.           el.QueryInterface(Ci.nsIFeedPerson);
  1639.         else
  1640.           return; // don't know about this interface
  1641.  
  1642.         var propName = localName;
  1643.         var prefix = gNamespaces[uri];
  1644.  
  1645.         // synonyms
  1646.         if ((uri == "" || 
  1647.              prefix &&
  1648.              ((prefix.indexOf("atom") > -1) ||
  1649.               (prefix.indexOf("rss") > -1))) && 
  1650.             (propName == "url" || propName == "href"))
  1651.           propName = "uri";
  1652.         
  1653.         try {
  1654.           if (el[propName] !== "undefined") {
  1655.             var propValue = chars;
  1656.             // convert URI-bearing values to an nsIURI
  1657.             if (propName == "uri") {
  1658.               var base = this._xmlBaseStack[this._xmlBaseStack.length - 1];
  1659.               propValue = strToURI(chars, base);
  1660.             }
  1661.             el[propName] = propValue;
  1662.           }
  1663.         }
  1664.         catch(e) {
  1665.           // ignore XPConnect errors
  1666.         }
  1667.         // the rest of the function deals with entry- and feed-level stuff
  1668.         return; 
  1669.       } 
  1670.       else {
  1671.         container = container.queryElementAt(container.length - 1, 
  1672.                                              Ci.nsIWritablePropertyBag2);
  1673.       }
  1674.     }
  1675.     
  1676.     // Make the buffer our new property
  1677.     var propName = this._prefixForNS(uri) + localName;
  1678.  
  1679.     // But, it could be something containing HTML. If so,
  1680.     // we need to know about that.
  1681.     if (this._textConstructs[propName] != null &&
  1682.         this._handlerStack[this._depth].containerClass !== null) {
  1683.       var newProp = Cc[TEXTCONSTRUCT_CONTRACTID].
  1684.                     createInstance(Ci.nsIFeedTextConstruct);
  1685.       newProp.text = chars;
  1686.       // Look up the default type in our table
  1687.       var type = this._textConstructs[propName];
  1688.       var typeAttribute = attributes.getValueFromName("","type");
  1689.       if (this._result.version == "atom" && typeAttribute != null) {
  1690.         type = typeAttribute;
  1691.       }
  1692.       else if (this._result.version == "atom03" && typeAttribute != null) {
  1693.         if (typeAttribute.toLowerCase().indexOf("xhtml") >= 0) {
  1694.           type = "xhtml";
  1695.         }
  1696.         else if (typeAttribute.toLowerCase().indexOf("html") >= 0) {
  1697.           type = "html";
  1698.         }
  1699.         else if (typeAttribute.toLowerCase().indexOf("text") >= 0) {
  1700.           type = "text";
  1701.         }
  1702.       }
  1703.       
  1704.       // If it's rss feed-level description, it's not supposed to have html
  1705.       if (this._result.version.indexOf("rss") >= 0 &&
  1706.           this._handlerStack[this._depth].containerClass != ENTRY_CONTRACTID) {
  1707.         type = "text";
  1708.       }
  1709.  
  1710.       newProp.type = type;
  1711.       newProp.base = this._xmlBaseStack[this._xmlBaseStack.length - 1];
  1712.       container.setPropertyAsInterface(propName, newProp);
  1713.     }
  1714.     else {
  1715.       container.setPropertyAsAString(propName, chars);
  1716.     }
  1717.     
  1718.   },
  1719.  
  1720.   // Sometimes, we'll hand off SAX handling duties to an XHTMLHandler
  1721.   // (see above) that will scrape out non-XHTML stuff, normalize
  1722.   // namespaces, and remove the wrapper div from Atom 1.0. When the
  1723.   // XHTMLHandler is done, it'll callback here.
  1724.   returnFromXHTMLHandler:
  1725.   function FP_returnFromXHTMLHandler(chars, uri, localName, qName) {
  1726.     // retake control of the SAX content events
  1727.     this._reader.contentHandler = this;
  1728.  
  1729.     // Grab the top of the stack
  1730.     var top = this._stack[this._stack.length - 1];
  1731.     if (!top) 
  1732.       return;
  1733.     var container = top[0];
  1734.  
  1735.     // Assign the property
  1736.     var newProp =  newProp = Cc[TEXTCONSTRUCT_CONTRACTID].
  1737.                    createInstance(Ci.nsIFeedTextConstruct);
  1738.     newProp.text = chars;
  1739.     newProp.type = "xhtml";
  1740.     newProp.base = this._xmlBaseStack[this._xmlBaseStack.length - 1];
  1741.     container.setPropertyAsInterface(this._prefixForNS(uri) + localName,
  1742.                                      newProp);
  1743.     
  1744.     // XHTML will cause us to peek too far. The XHTML handler will
  1745.     // send us an end element to call. RFC4287-valid feeds allow a
  1746.     // more graceful way to handle this. Unfortunately, we can't count
  1747.     // on compliance at this point.
  1748.     this.endElement(uri, localName, qName);
  1749.   },
  1750.  
  1751.   // nsISupports
  1752.   QueryInterface: function FP_QueryInterface(iid) {
  1753.     if (iid.equals(Ci.nsIFeedProcessor) ||
  1754.         iid.equals(Ci.flockIFeedProcessor) ||
  1755.         iid.equals(Ci.nsISAXContentHandler) ||
  1756.         iid.equals(Ci.nsISAXErrorHandler) ||
  1757.         iid.equals(Ci.nsIStreamListener) ||
  1758.         iid.equals(Ci.nsIRequestObserver) ||
  1759.         iid.equals(Ci.nsISupports))
  1760.       return this;
  1761.  
  1762.     throw Cr.NS_ERROR_NOINTERFACE;
  1763.   },
  1764. }
  1765.  
  1766. const FP_CONTRACTID = "@mozilla.org/feed-processor;1";
  1767. const FP_CLASSID = Components.ID("{26acb1f0-28fc-43bc-867a-a46aabc85dd4}");
  1768. const FP_CLASSNAME = "Feed Processor";
  1769. const FR_CONTRACTID = "@mozilla.org/feed-result;1";
  1770. const FR_CLASSID = Components.ID("{072a5c3d-30c6-4f07-b87f-9f63d51403f2}");
  1771. const FR_CLASSNAME = "Feed Result";
  1772. const FEED_CONTRACTID = "@mozilla.org/feed;1";
  1773. const FEED_CLASSID = Components.ID("{5d0cfa97-69dd-4e5e-ac84-f253162e8f9a}");
  1774. const FEED_CLASSNAME = "Feed";
  1775. const ENTRY_CONTRACTID = "@mozilla.org/feed-entry;1";
  1776. const ENTRY_CLASSID = Components.ID("{8e4444ff-8e99-4bdd-aa7f-fb3c1c77319f}");
  1777. const ENTRY_CLASSNAME = "Feed Entry";
  1778. const TEXTCONSTRUCT_CONTRACTID = "@mozilla.org/feed-textconstruct;1";
  1779. const TEXTCONSTRUCT_CLASSID =
  1780.   Components.ID("{b992ddcd-3899-4320-9909-924b3e72c922}");
  1781. const TEXTCONSTRUCT_CLASSNAME = "Feed Text Construct";
  1782. const GENERATOR_CONTRACTID = "@mozilla.org/feed-generator;1";
  1783. const GENERATOR_CLASSID =
  1784.   Components.ID("{414af362-9ad8-4296-898e-62247f25a20e}");
  1785. const GENERATOR_CLASSNAME = "Feed Generator";
  1786. const PERSON_CONTRACTID = "@mozilla.org/feed-person;1";
  1787. const PERSON_CLASSID = Components.ID("{95c963b7-20b2-11db-92f6-001422106990}");
  1788. const PERSON_CLASSNAME = "Feed Person";
  1789.  
  1790. function GenericComponentFactory(ctor) {
  1791.   this._ctor = ctor;
  1792. }
  1793.  
  1794. GenericComponentFactory.prototype = {
  1795.  
  1796.   _ctor: null,
  1797.  
  1798.   // nsIFactory
  1799.   createInstance: function(outer, iid) {
  1800.     if (outer != null)
  1801.       throw Cr.NS_ERROR_NO_AGGREGATION;
  1802.     return (new this._ctor()).QueryInterface(iid);
  1803.   },
  1804.  
  1805.   // nsISupports
  1806.   QueryInterface: function(iid) {
  1807.     if (iid.equals(Ci.nsIFactory) ||
  1808.         iid.equals(Ci.nsISupports))
  1809.       return this;
  1810.     throw Cr.NS_ERROR_NO_INTERFACE;
  1811.   },
  1812.  
  1813. };
  1814.  
  1815. var Module = {
  1816.   QueryInterface: function(iid) {
  1817.     if (iid.equals(Ci.nsIModule) || 
  1818.         iid.equals(Ci.nsISupports))
  1819.       return this;
  1820.  
  1821.     throw Cr.NS_ERROR_NO_INTERFACE;
  1822.   },
  1823.  
  1824.   getClassObject: function(cm, cid, iid) {
  1825.     if (!iid.equals(Ci.nsIFactory))
  1826.       throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  1827.  
  1828.     if (cid.equals(FP_CLASSID))
  1829.       return new GenericComponentFactory(FeedProcessor);
  1830.     if (cid.equals(FR_CLASSID))
  1831.       return new GenericComponentFactory(FeedResult);
  1832.     if (cid.equals(FEED_CLASSID))
  1833.       return new GenericComponentFactory(Feed);
  1834.     if (cid.equals(ENTRY_CLASSID))
  1835.       return new GenericComponentFactory(Entry);
  1836.     if (cid.equals(TEXTCONSTRUCT_CLASSID))
  1837.       return new GenericComponentFactory(TextConstruct);
  1838.     if (cid.equals(GENERATOR_CLASSID))
  1839.       return new GenericComponentFactory(Generator);
  1840.     if (cid.equals(PERSON_CLASSID))
  1841.       return new GenericComponentFactory(Person);
  1842.  
  1843.     throw Cr.NS_ERROR_NO_INTERFACE;
  1844.   },
  1845.  
  1846.   registerSelf: function(cm, file, location, type) {
  1847.     var cr = cm.QueryInterface(Ci.nsIComponentRegistrar);
  1848.     // Feed Processor
  1849.     cr.registerFactoryLocation(FP_CLASSID, FP_CLASSNAME,
  1850.       FP_CONTRACTID, file, location, type);
  1851.     // Feed Result
  1852.     cr.registerFactoryLocation(FR_CLASSID, FR_CLASSNAME,
  1853.       FR_CONTRACTID, file, location, type);
  1854.     // Feed
  1855.     cr.registerFactoryLocation(FEED_CLASSID, FEED_CLASSNAME,
  1856.       FEED_CONTRACTID, file, location, type);
  1857.     // Entry
  1858.     cr.registerFactoryLocation(ENTRY_CLASSID, ENTRY_CLASSNAME,
  1859.       ENTRY_CONTRACTID, file, location, type);
  1860.     // Text Construct
  1861.     cr.registerFactoryLocation(TEXTCONSTRUCT_CLASSID, TEXTCONSTRUCT_CLASSNAME,
  1862.       TEXTCONSTRUCT_CONTRACTID, file, location, type);
  1863.     // Generator
  1864.     cr.registerFactoryLocation(GENERATOR_CLASSID, GENERATOR_CLASSNAME,
  1865.       GENERATOR_CONTRACTID, file, location, type);
  1866.     // Person
  1867.     cr.registerFactoryLocation(PERSON_CLASSID, PERSON_CLASSNAME,
  1868.       PERSON_CONTRACTID, file, location, type);
  1869.   },
  1870.  
  1871.   unregisterSelf: function(cm, location, type) {
  1872.     var cr = cm.QueryInterface(Ci.nsIComponentRegistrar);
  1873.     // Feed Processor
  1874.     cr.unregisterFactoryLocation(FP_CLASSID, location);
  1875.     // Feed Result
  1876.     cr.unregisterFactoryLocation(FR_CLASSID, location);
  1877.     // Feed
  1878.     cr.unregisterFactoryLocation(FEED_CLASSID, location);
  1879.     // Entry
  1880.     cr.unregisterFactoryLocation(ENTRY_CLASSID, location);
  1881.     // Text Construct
  1882.     cr.unregisterFactoryLocation(TEXTCONSTRUCT_CLASSID, location);
  1883.     // Generator
  1884.     cr.unregisterFactoryLocation(GENERATOR_CLASSID, location);
  1885.     // Person
  1886.     cr.unregisterFactoryLocation(PERSON_CLASSID, location);
  1887.   },
  1888.  
  1889.   canUnload: function(cm) {
  1890.     return true;
  1891.   },
  1892. };
  1893.  
  1894. function NSGetModule(cm, file) {
  1895.   return Module;
  1896. }
  1897.